package im.composer.media.sound.codec.seek;

import java.io.InputStream;
import java.lang.reflect.Field;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Map.Entry;
import java.util.concurrent.Callable;

import org.kc7bfi.jflac.FLACDecoder;
import org.kc7bfi.jflac.PCMProcessor;
import org.kc7bfi.jflac.frame.Frame;
import org.kc7bfi.jflac.metadata.SeekPoint;
import org.kc7bfi.jflac.metadata.StreamInfo;
import org.kc7bfi.jflac.util.ByteData;
import org.tritonus.share.sampled.FloatInputStream;

import im.composer.io.MyFiles;
import im.composer.media.audio.stream.AudioByteBuffer;

public class FLACSeekSupport extends AbstractSeekSupport {

	private final SeekableByteChannel sbc;
	private FLACDecoder decoder;
	private AudioByteBuffer abb;

	public FLACSeekSupport(Path path, SeekableByteChannel sbc) {
		super(path);
		this.sbc = sbc;
	}

	@Override
	public synchronized void seek(long sample_position) {
		if (state == SeekSupportState.UNSUPPORTED) {
			return;
		}
		Entry<Long, Long> entry = null;
		do {
			entry = pos_map.floorEntry(sample_position);
			if (entry == null && state == SeekSupportState.BUILDING) {
				try {
					Thread.sleep(0);
				} catch (InterruptedException e) {
					continue;
				}
			}
		} while (entry == null);
		if (entry != null) {
			try {
				sbc.position(entry.getValue());
				PCMProcessor pp = new PCMProcessor() {

					@Override
					public void processStreamInfo(StreamInfo streamInfo) {

					}

					@Override
					public void processPCM(ByteData pcm) {
						byte[] b_arr = Arrays.copyOf(pcm.getData(), pcm.getLen());
						abb.offer(b_arr);
					}
				};
				SeekPoint sp = new SeekPoint(entry.getKey(), entry.getValue(), -1);
				decoder.addPCMProcessor(pp);
				decoder.decode(sp, sp);
				decoder.removePCMProcessor(pp);
				Field field = FloatInputStream.class.getDeclaredField("position");
				field.setAccessible(true);
				field.set(fis, entry.getKey());
				fis.skip(sample_position - entry.getKey());
			} catch (Exception e) {
			}
		}
	}

	public void setDecoder(FLACDecoder decoder) {
		this.decoder = decoder;
	}

	public void setAudioByteBuffer(AudioByteBuffer abb) {
		this.abb = abb;
	}

	@Override
	protected Callable<Void> buildWorkder() {
		return () -> {
			InputStream in = MyFiles.getEnhancedInputStream(path);
			FLACDecoder decoder = new FLACDecoder(in);
			decoder.readStreamInfo();
			long pos = 0;
			while (!decoder.isEOF()) {
				pos = decoder.getTotalBytesRead();
				Frame frame = decoder.readNextFrame();
				if (frame == null) {
					break;
				}
				pos_map.put(frame.header.sampleNumber, pos);
			}
			state = SeekSupportState.SUPPORT;
			writeSeekIndex();
			return null;
		};
	}

}
